package sbc.group1.lab1.mozartspaces;

import static org.mozartspaces.core.MzsConstants.Selecting.isSpecialCountConstant;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import org.mozartspaces.capi3.Capi3Exception;
import org.mozartspaces.capi3.CoordinationData;
import org.mozartspaces.capi3.CoordinatorLockedException;
import org.mozartspaces.capi3.CountNotMetException;
import org.mozartspaces.capi3.EntryLockedException;
import org.mozartspaces.capi3.IsolationLevel;
import org.mozartspaces.capi3.OperationType;
import org.mozartspaces.capi3.Selector;
import org.mozartspaces.capi3.javanative.LockedExceptionsHelper;
import org.mozartspaces.capi3.javanative.coordination.AbstractDefaultSelector;
import org.mozartspaces.capi3.javanative.coordination.NativeCoordinator;
import org.mozartspaces.capi3.javanative.coordination.NativeCoordinatorTranslator;
import org.mozartspaces.capi3.javanative.coordination.NativeSelectorTranslator;
import org.mozartspaces.capi3.javanative.isolation.Availability;
import org.mozartspaces.capi3.javanative.isolation.NativeIsolationManager;
import org.mozartspaces.capi3.javanative.isolation.NativeSubTransaction;
import org.mozartspaces.capi3.javanative.operation.NativeEntry;
import org.mozartspaces.core.RequestContext;
import org.mozartspaces.core.metamodel.MetaDataProvider;
import org.mozartspaces.core.metamodel.MetaModelKeys;
import org.mozartspaces.core.metamodel.MetaModelUtils;
import org.mozartspaces.core.metamodel.Navigable;
import org.mozartspaces.core.metamodel.MetaModelKeys.Containers.Container.Coordinators;
import org.mozartspaces.core.metamodel.MetaModelUtils.MethodTuple;

import sbc.group1.lab1.mozartspaces.ColorCoordinator.ColorData;
import sbc.group1.lab1.mozartspaces.ColorCoordinator.ColorSelector;


public final class DefaultColorCoordinator implements NativeCoordinator, Navigable, MetaDataProvider {

    private static final long serialVersionUID = 2943565620010655641L;

    private final String name;

    @SuppressWarnings("unused")
    private NativeIsolationManager isolationManager;

    private final Map<java.awt.Color, List<NativeEntry>> colors;
    private final Map<NativeEntry, java.awt.Color> entries;

    private final Map<String, Object> metaModel;
    private static final Method GET_LABEL_SET_METHOD;

    static {
        try {
            GET_LABEL_SET_METHOD = DefaultColorCoordinator.class.getDeclaredMethod("getLabelSet", (Class<?>[]) null);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    /**
     * Creates a DefaultLabelCoordinator.
     *
     * @param name
     *            of the Coordinator
     * @param isKeyCoordinator
     *            flag that the coordinator should use the KeyCoordinator semantics, that is, allow only only one entry
     *            per label
     */
    public DefaultColorCoordinator(final String name) {
        this.name = name;
        this.colors = new ConcurrentHashMap<java.awt.Color, List<NativeEntry>>();
        this.entries = new ConcurrentHashMap<NativeEntry, java.awt.Color>();
        
        metaModel = new HashMap<String, Object>();
        metaModel.put(MetaModelKeys.Containers.Container.Coordinators.NAME, name);
        metaModel.put(MetaModelKeys.Containers.Container.Coordinators.CLASS, getClass().getName());
        metaModel.put(Coordinators.ENTRYCOUNT,
                new MethodTuple(MetaModelUtils.CONCURRENT_HASH_MAP_SIZE_METHOD, entries));
        
        metaModel.put(MetaModelKeys.Containers.Container.Coordinators.LabelCoordinatorMeta.LABELS, new MethodTuple(
                    GET_LABEL_SET_METHOD, this));
        metaModel.put(MetaModelKeys.Containers.Container.Coordinators.ENTRIES, colors);
    }

    // only for meta model
    @SuppressWarnings("unused")
    private Set<java.awt.Color> getLabelSet() {
        return new HashSet<java.awt.Color>(colors.keySet());
    }

    /**
     * @return the internal label map
     */
    Map<java.awt.Color, List<NativeEntry>> getLabels() {
        return colors;
    }

    /**
     * @return the internal entry map
     */
    Map<NativeEntry, java.awt.Color> getEntries() {
        return entries;
    }

    @Override
    public synchronized boolean registerEntry(final NativeSubTransaction stx, final CoordinationData coordData,
            final NativeEntry entry, final RequestContext context) throws Capi3Exception {

        // store entry first in labels and then in entries
        // implication for the (not synchronized) selection: if an entry is in entries it is also in labels
        java.awt.Color color = ((ColorData) coordData).getColor();
        if (color!=null) {
            List<NativeEntry> data = this.colors.get(color);
            if (data == null) {
                data = new CopyOnWriteArrayList<NativeEntry>();
                this.colors.put(color, data);
            }
            data.add(entry);

            this.entries.put(entry, color);
        }
        return true;
    }

    @Override
    public synchronized boolean unregisterEntry(final NativeEntry entry, final RequestContext context) {
        java.awt.Color label = this.entries.remove(entry);
        if (label == null) {
            return true;
        }
        List<NativeEntry> data = this.colors.get(label);
        if (data.size() == 1) {
            // remove the whole entry list
            this.colors.remove(label);
        } else {
            // remove the entry from the entry list
            data.remove(entry);
        }
        return true;
    }

    @Override
    public void prepareEntryRemoval(final NativeSubTransaction stx, final NativeEntry entry,
            final RequestContext context) throws CoordinatorLockedException {
        return;
    }

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void setIsolationManager(final NativeIsolationManager isolationManager) {
        this.isolationManager = isolationManager;
    }

    /**
     * Creates a DefaultLabelSelector.
     *
     * @param name
     *            of the Coordinator this Selector is associated to
     * @param count
     *            of entries to select
     * @param label
     *            to be selected
     * @return new DefaultLabelSelector
     */
    public static DefaultColorSelector newSelector(final String name, final int count, final java.awt.Color color) {
        return new DefaultColorSelector(name, count, color);
    }


    private static final class DefaultColorSelector extends AbstractDefaultSelector {

        private static final long serialVersionUID = -6456339477248929002L;

        @SuppressWarnings("unused")
        private DefaultColorCoordinator coordinator;

        // the label to select
        private final java.awt.Color color;

        // the current count (important state for getNext)
        private int currentCount;
        // entry iterator (used in getNext to ensure that entries are returned only once in subsequent calls)
        private Iterator<NativeEntry> iterator;
        // stores information that an entry is skipped because it is locked
        private Availability entryLockedAvailability;
        // from the coordinator
        private Map<java.awt.Color, List<NativeEntry>> labels;
        private Map<NativeEntry, java.awt.Color> entries;
        /**
         * Creates a DefaultLabelSelector.
         *
         * @param name
         *            of the Coordinator this Selector is associated to
         * @param count
         *            of entries
         * @param label
         *            to be selected
         */
        private DefaultColorSelector(final String name, final int count, final java.awt.Color color) {
            super(name, count);
            this.color = color;
        }

        @Override
        protected List<NativeEntry> getAllFirstSelector(final IsolationLevel isolationLevel,
                final NativeSubTransaction stx, final OperationType opType) throws CountNotMetException,
                EntryLockedException {
            
            List<NativeEntry> data;
            if (color==null) {
                return new ArrayList<NativeEntry>();
            }else {
                data = this.labels.get(this.color);
            }
            if (data == null) {
                // no entries with that label
                checkCount(0, this.getCount(), this.getName());
                return new ArrayList<NativeEntry>();
            }
            checkCount(data.size(), this.getCount(), this.getName());

            List<NativeEntry> result = new ArrayList<NativeEntry>();
            // iterate over all entries with the label
            for (NativeEntry entry : data) {
                if (isCountMet(result.size(), this.getCount())) {
                    return result;
                }
                // check whether the entry is available
                Availability availability = super.getIsolationManager().checkEntryAvailability(entry, isolationLevel,
                        stx, opType);
                switch (availability.getType()) {
                case AVAILABLE:
                    result.add(entry);
                    break;
                case NOTVISIBLE:
                    // insert lock on the entry
                    break;
                case NOTAVAILABLE:
                    // the operation would not be able to acquire a lock
                    if (this.getCount() == Selector.COUNT_ALL) {
                        // COUNT_ALL not satisfiable
                        throw LockedExceptionsHelper.newEntryLockedException(availability);
                    }
                    entryLockedAvailability = availability;
                    break;
                default:
                    throw new IllegalStateException("This state should not be reachable");
                }
            }
            checkCount(result.size(), this.getCount(), entryLockedAvailability, this.getName());
            return result;
        }

        @Override
        protected List<NativeEntry> getAllSelectorInside(final IsolationLevel isolationLevel,
                final NativeSubTransaction stx, final OperationType opType) throws CountNotMetException,
                EntryLockedException {

            List<NativeEntry> result = new ArrayList<NativeEntry>();
            while (true) {
                if (isCountMet(result.size(), this.getCount())) {
                    return result;
                }
                NativeEntry entry = super.getPredecessor().getNext(isolationLevel, stx, opType);
                if (entry == null) {
                    checkCount(result.size(), this.getCount(), this.getName());
                    return result;
                }
                // check that entry is registered at this coordinator and has the correct label
                java.awt.Color entryLabel = this.entries.get(entry);
                if (entryLabel != null && entryLabel.equals(this.color)) {
                    result.add(entry);
                }
            }
        }

        @Override
        protected NativeEntry getNextFirstSelector(final IsolationLevel isolationLevel, final NativeSubTransaction stx,
                final OperationType opType) throws CountNotMetException, EntryLockedException {

            if (isCountMet(currentCount, this.getCount())) {
                return null;
            }

            // ensure that we have an entry iterator
            if (this.iterator == null) {
                if (!isSpecialCountConstant(this.getCount()) || this.getCount() == Selector.COUNT_ALL) {
                    // getAll ensures that we have enough entries in the coordinator
                    this.iterator = this.getAll(isolationLevel, stx, opType).iterator();
                } else {
                    // optimization for COUNT_MAX
                    List<NativeEntry> data = null;
                    if (color!=null) {
                        data = this.labels.get(this.color);
                    }
                    if (data == null) {
                        checkCount(0, this.getCount(), this.getName());
                        this.iterator = new ArrayList<NativeEntry>().iterator();
                    } else {
                        checkCount(data.size(), this.getCount(), this.getName());
                        this.iterator = data.iterator();
                    }
                }
            }

            // use loop because locked entries can be skipped
            while (true) {
                // get the next entry
                if (!this.iterator.hasNext()) {
                    return null;
                }
                NativeEntry entry = this.iterator.next();
                // check whether the entry is available
                Availability availability = super.getIsolationManager().checkEntryAvailability(entry, isolationLevel,
                        stx, opType);
                switch (availability.getType()) {
                case AVAILABLE:
                    this.currentCount++;
                    return entry;
                case NOTVISIBLE:
                    // insert lock on the entry
                    break;
                case NOTAVAILABLE:
                    // the operation would not be able to acquire a lock
                    if (this.getCount() == Selector.COUNT_ALL) {
                        // COUNT_ALL not satisfiable
                        throw LockedExceptionsHelper.newEntryLockedException(availability);
                    }
                    entryLockedAvailability = availability;
                    break;
                default:
                    throw new IllegalStateException("This state should not be reachable");
                }

                checkCount(currentCount, this.getCount(), entryLockedAvailability, this.getName());
            }
        }

        @Override
        protected NativeEntry getNextSelectorInside(final IsolationLevel isolationLevel, final NativeSubTransaction stx,
                final OperationType opType) throws CountNotMetException, EntryLockedException {

            // ensure that we have an entry iterator
            if (this.iterator == null) {
                // we cache the result up to this selector
                // getAll ensures that we have enough entries in the coordinator and returns 'count' entries
                List<NativeEntry> result = this.getAll(isolationLevel, stx, opType);
                this.iterator = result.iterator();
            }

            if (!this.iterator.hasNext()) {
                return null;
            }
            return this.iterator.next();

        }

        @Override
        public void link(final NativeCoordinator coordinator, final NativeIsolationManager isolationManager) {
            super.setIsolationManager(isolationManager);
            this.coordinator = (DefaultColorCoordinator) coordinator;
            this.labels = ((DefaultColorCoordinator) coordinator).getLabels();
            this.entries = ((DefaultColorCoordinator) coordinator).getEntries();
        }

        @Override
        public String toString() {
            return "Label " + getName() + " (label=" + color + ", count=" + getCount() + ")";
        }

    }

    @Override
    public Object navigate(final String path) {
        return MetaModelUtils.navigate(path, this, metaModel);
    }

    @Override
    public Object getMetaData(final int depth) {
        return MetaModelUtils.getData(depth, metaModel);
    }

    @Override
    public void setMetaDataProperty(final String key, final Object value) {
        throw new UnsupportedOperationException("Properties cannot be set here");
    }
    
    /**
     * Translates between the API and implementation class of the coordinator.
     *
     * @author Tobias Doenz
     */
    public static final class ColorCoordinatorTranslator implements
            NativeCoordinatorTranslator<ColorCoordinator, DefaultColorCoordinator> {

        @Override
        public DefaultColorCoordinator translateCoordinator(final ColorCoordinator coordinator) {
            return new DefaultColorCoordinator(coordinator.getName());
        }
    }

    /**
     * Translates between the API and implementation class of the selector.
     *
     * @author Tobias Doenz
     */
    public static final class ColorSelectorTranslator implements
            NativeSelectorTranslator<ColorSelector, DefaultColorSelector> {

        @Override
        public DefaultColorSelector translateSelector(final ColorSelector selector) {
            return DefaultColorCoordinator.newSelector(selector.getName(),
                    selector.getCount(),selector.getColor());
        }
    }

}